ATTENTION READERS! Lucky's VB Gaming Site
is no longer active. For updated game programming information
and tutorials, please visit The
Game Programming Wiki!
Pixel plotting
(This tutorial is translated by Lorenz "Paladin"
Barnkow)
Hello! This tutorial is written by Stephan
Kirchmaier. The original File can be found at http://www.vb-empire.de.vu/. There are several
ways to plot single pixels, but most of them are very slow. In
this tutorial I'm going to show you four different ways.
There's also a sample project, that compares all these
methods. I'll only describe everything for 24 bit
colordepth. First we load a Picture into PictureBox and set
the ScaleMode property to 3 (vbPixels):
Set
Picture1.Picture = LoadPicture(<Path of your
File>)
Let's start:
1) PSet and Point
'Point' reads a color value from a given
position. 'PSet' plots a pixel to a given position in any
given color value. Now we can alter the whole picture:
For i = 0
To Picture1.ScaleWidth For j = 0 To
Picture1.ScaleHeight
Col = Picture1.Point(i,
j) Col =
Abs(Col) \ 2
Picture1.PSet (i, j), Col Next j Next
i
This code reads every pixel from the top left
corner, halfs the color value, and paints it again with new
color. We use 'Abs(Col)' because the color value can alos be
'-1'. This means the color value of this pixel is not
available.
2) SetPixel and GetPixel
These are just two API functions. They both work
the same way as 'PSet' and 'Point', but they are much faster.
Here are the declarations:
Declare
Function GetPixel Lib "gdi32" Alias "GetPixel" (ByVal hdc As
Long, ByVal x As Long, ByVal y As Long) As Long Declare
Function SetPixel Lib "gdi32" Alias "SetPixel" (ByVal hdc As
Long, ByVal x As Long, ByVal y As Long, ByVal crColor As Long)
As Long
We'll need the 'DeviceContext' of our PictureBox
for these functions. You can get the DC from the hDC property
of the PictureBox.
For i = 0
To Picture1.ScaleWidth For j = 0 To
Picture1.ScaleHeight
col = GetPixel(Picture1.hdc, i,
j) col =
Abs(col) \ 2
SetPixel Picture1.hdc, i, j, col Next
j Next i
This Code will be executed somewhat faster than
before, but it is still too slow for any real application. A
bit slower is the usage of the 'SetPixel' function. It works
the same way as the 'SetPixel' function. It doesn't display
the correct color, but a color similar to the original one
(you may not recognize the difference).
Declare
Function SetPixelV Lib "gdi32" Alias "SetPixelV" (ByVal hdc As
Long, ByVal x As Long, ByVal y As Long, ByVal crColor As Long)
As Long
3) SetPixel and GetPixel with a created
DC
This is a pretty complex way, but it'll speed up
everything some milliseconds. First we creat a DeviceContext
and a compatible bitmap. Then you select the bitmap over its
DC. This means, that now you cann access your bitmap by using
this DC. Then you copy your whole picture into that bitmap,
and modify it there. After this you copy it back. This is
faster but also harder to deal with:
We'll need these API functions:
Private
Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long,
ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal
nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long,
ByVal ySrc As Long, ByVal dwRop As Long) As Long Private
Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hdc
As Long, ByVal nWidth As Long, ByVal nHeight As Long) As
Long Private Declare Function CreateCompatibleDC Lib
"gdi32" (ByVal hdc As Long) As Long Private Declare
Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal
hObject As Long) As Long Private Declare Function DeleteDC
Lib "gdi32" (ByVal hdc As Long) As Long Private Declare
Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As
Long
'BitBlt' copies the Picture into the bitmap and
back. 'CreateCompatibleBitmap' reserves memory for our
bitmap. 'CreateCompatibleDC' creates the
DeviceContext. 'SelectObject' selects the bitmap into the
DeviceContext. 'DeleteDC' releases the
DeviceContext. 'DeleteObject' releases the bitmap
memory.
Dim mDC,
mBMP
mDC = CreateCompatibleDC(Picture1.hdc) mBMP =
CreateCompatibleBitmap(Picture1.hdc, Picture1.ScaleWidth,
Picture1.ScaleHeight) SelectObject mDC, mBMP BitBlt mDC,
0, 0, sw, sh, Picture1.hdc, 0, 0, vbSrcCopy For i = 0 To
Picture1.ScaleWidth For j = 0 To
Picture1.ScaleHeight
col = GetPixel(mDC, i,
j) col =
Abs(col) \ 2
SetPixel mDC, i, j, col Next j Next
i BitBlt Picture1.hdc, 0, 0, Picture1.ScaleWidth,
Picture1.ScaleHeight, mDC, 0, 0, vbSrcCopy DeleteObject
mBMP DeleteDC mDC
As above you may use the 'SetPixelV' function
instead of 'SetPixel'.
4) Use a Pointer
First some declarations:
Option
Explicit
Type SAFEARRAYBOUND
cElements As Long lLbound As Long End
Type
Type
SAFEARRAY2D cDims As
Integer fFeatures As
Integer cbElements As
Long cLocks As
Long pvData As
Long Bounds(0 To 1) As
SAFEARRAYBOUND End Type
Type
BITMAP bmType As
Long bmWidth As
Long bmHeight As
Long bmWidthBytes As
Long bmPlanes As
Integer bmBitsPixel As
Integer bmBits As Long End
Type
Declare Function VarPtrArray Lib "msvbvm50.dll"
Alias "VarPtr" (Ptr() As Any) As Long Declare Sub
CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDst As Any,
pSrc As Any, ByVal ByteLen As Long) Declare Function
GetObjectAPI Lib "gdi32" Alias "GetObjectA" (ByVal hObject As
Long, ByVal nCount As Long, lpObject As Any) As
Long
The user defined type 'SAFEARRAY2D' is used by
Visual Basic for internal management multiple dimension
arrays. The user defined type 'BITMAP' will keep some
information about our picture. 'VarPtrArray' returns the
memory address of an array. 'CopyMemory' copies blocks in
memory from one position to another (extremely
fast). 'GetObjectAPI' returns information about our bitmap,
that will be written into our user defined type 'BITMAP'.
Dim pic()
As Byte Dim sa As SAFEARRAY2D Dim bmp As BITMAP Dim r
As Long, g As Long, b As Long
We'll need these variables. Our whole picture
will be saved to 'pic()'. With 'sa' we make Visual Basic think
we got an array. 'bmp' will be filled with the information
about our Picture. 'r', 'g' and 'b' will contain the color
values of each channel, which are taken from 'pic()'.
GetObjectAPI Picture1.Picture, Len(bmp), bmp
Now we got the information about our bitmap, and
stored it into 'bmp'.
With
sa .cbElements = 1
.cDims = 2 .Bounds(0).lLbound =
0 .Bounds(0).cElements =
bmp.bmHeight .Bounds(1).lLbound =
0 .Bounds(1).cElements =
bmp.bmWidthBytes .pvData =
bmp.bmBits End With
Now we fill 'sa' with data. A two dimensional
array is created. The upper bound of the first dimension is
the picture height in pixels. The upper bound of the second
dimension is three time the picture width, since every pixel
is created of three colors. 'sa.pvData' now points to the
bimap data.
CopyMemory
ByVal VarPtrArray(pic), VarPtrArray(sa), 4
We now overwrite 'pic()', with our 'sa'
array.
For i = 0
To UBound(pic, 1) For j = 0 To
UBound(pic, 2)
pic(i, j) = 255 - pic(i, j) Next
j Next i
We'll use to loops, to alter our array. The code
shown above inverts the picture. If you want to change the
red, green, and blue values separately, you could do it like
this:
For i = 0
To UBound(pic, 1) - 3 Step 3 For j = 0
To UBound(pic,
2) r = pic(i +
2, j) g = pic(i
+ 1, j) b =
pic(i, j) r =
((g * b) \ 128)
g = ((r * b) \
128) b = ((r *
g) \ 128) If r
> 255 Then r =
255 If r < 0
Then r = 0 If g
> 255 Then g =
255 If g < 0
Then g = 0 If b
> 255 Then b =
255 If b < 0
Then b = 0
pic(i, j) = b
pic(i + 1, j) =
g pic(i + 2, j)
= r Next j Next i
It also may be interesting how the data is stored in our
array. Well, they're stored from the bottom left to the upper
right corner, as this table shows you:
| 1 |
b |
g |
r |
b |
g |
r |
b |
g |
r |
| 0 |
b |
g |
r |
b |
g |
r |
b |
g |
r |
| |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
So pic(0, 0) contains the blue value of the bottom
left pixel. RGB(pic(0, 2), pic(0,1), pic(0,0)) will return
its color.
CopyMemory ByVal
VarPtrArray(pic), 0&, 4
You should delete your array after your changes, like in
the line shown above.
Picture1.Refresh
Now you can refresh you PictureBox, since all changes have
been accomplished in the system memory.
This method allows really fast pixel manipulation, that
could also be used for games. I also created a table to
show you the speed differences. The picture was 433 by 263
pixels large and has been inverted every time. My PC is a P2
MMX at 300 MHz and 96 MB RAM. Every test has been repeated
five times. Then I calculated a middle of all the five tests,
and here they are:
| PSet and
Point |
3737,6
ms |
| GetPixel and
SetPixel |
3133,4
ms |
| GetPixel and
SetPixelV |
3210,4
ms |
| GetPixel and
SetPixel with created DC |
2032,4
ms |
| GetPixel and
SetPixelV with created DC |
1936,0
ms |
| Pointer |
222,0
ms |
As you can see, the 'Pointer' method a lot
faster than any other. That's why you should use it, when ever
you need pixel plotting in you programs.
Any questions or suggestion? Mail to: VB_Empire@gmx.at
|